﻿using System;
using System.Reflection;
using metroioc.Container;

namespace MetroIoc
{
    partial class MetroContainer
    {
        /// <summary>
        /// Register a type within the container. Useful when registering a type by key, so calls to Resolve(null, key) will work
        /// </summary>
        /// <typeparam name="T">The type of class being registered</typeparam>
        /// <param name="key">If specified, will associate a specific instance for this type</param>
        /// <param name="registration"></param>
        /// <returns>The container, complete with new registration</returns>
        public IContainer Register<T>(string key = null, IRegistration registration = null)
        {
            return RegisterImpl(typeof(T), typeof(T), key, registration);
        }

        public IContainer Register<T>(Type type, string key = null, IRegistration registration = null)
        {
            var infoForT = typeof(T).GetTypeInfo();
            var infoForType = type.GetTypeInfo();

            if (!infoForT.IsAssignableFrom(infoForType))
                throw new RegistrationException(string.Format("{0} must implement {1}", infoForType.Name, infoForT.Name));
            return RegisterImpl(typeof(T), type, key, registration);
        }

        /// <summary>
        /// Register an implementation type against an interface or class
        /// </summary>
        /// <typeparam name="TFrom">The type of interface or class to be registered</typeparam>
        /// <typeparam name="TTo">The type of concrete class to be instantiated when <see cref="TFrom" /> is resolved from the container.</typeparam>
        /// <returns>The container, complete with new registration</returns>
        public IContainer Register<TFrom, TTo>(string key = null, IRegistration registration = null) where TTo : TFrom
        {
            return RegisterImpl(typeof(TFrom), typeof(TTo), key, registration);
        }

        public IContainer Register(Type fromType, Type toType, string key = null, IRegistration registration = null)
        {
            return RegisterImpl(fromType, toType, key, registration);
        }

        /// <summary>
        /// Register a specific instance of a concrete implementation for an interface or class
        /// </summary>
        /// <typeparam name="TInterface">The type of interface or class to be registered</typeparam>
        /// <param name="instance">The instance to register in the container</param>
        /// <param name="key">(Optional) a key to specify the instance within the container</param>
        /// <returns>The container, complete with new registration</returns>
        public IContainer RegisterInstance<TInterface>(TInterface instance, string key = null)
        {
            return RegisterInstance(typeof(TInterface), instance, key);
        }

        public IContainer RegisterInstance(Type type, object instance, string key = null)
        {
            key = key.ValueOrDefault(CollectionDefaultKey);

            return RegisterImpl(type, instance.GetType(), key, new Singleton(), instance);
        }

        private IContainer RegisterImpl(Type fromType, Type toType, string key, IRegistration registration, object instance = null)
        {
            key = key.ValueOrDefault(CollectionDefaultKey);
            if (registration == null)
                registration = new Transient();

            Func<LifetimeScope, object> resolverFunc = null;
            if (instance == null)
            {
                resolverFunc = res => InstantiateInstance(toType, res);
                registration.Register(fromType, key, resolverFunc);
            }
            else
            {
                //  If we have an instance, then it *must* be a Singleton life-cycle
                var singletonlifecycle = registration as Singleton;
                if (singletonlifecycle == null)
                    throw new InvalidOperationException("Cannot register an instance against any life cycle except singleton");
                singletonlifecycle.Register(fromType, key, instance);
            }

            var r = new TypeResolver { Registration = registration, ResolveInstance = resolverFunc };
            scope.Set(fromType, key, r);

            return this;
        }
    }
}
